/*
 * debug option pre-defined before the include file 'sys/debug.h'.
 */
//#define __DBG_THIS_MODULE__
#define PFX	"udc: "

#include "config.h"
#include "usb.h"
#include "ictl.h"
//#include "sys/debug.h"


#define debug

//#define udc_cpu_relax()		cpu_relax()

extern void cdc_reset(void);
extern char *cdc_req_descriptor(device_req_t *preq);
extern void cdc_out_completed(char *outbuf, int len);
extern int cdc_in_completed(char *inbuf, int len);

extern void class_request(uint8_t bRequest);
extern const usb_setup_t usb_setup;

uint8_t usb_running_state;
static device_req_t reqs;
static const char *p_ctrl_buf;
static get_status_t get_status;
static uint8_t remote_wakeup;
static uint16_t config_value;
static get_intf_t get_intf;

static void t1_udc_reset(void)
{
        int n;

        usb_running_state = USB_RESET;//USB_IDLE;
        cdc_reset();

        udc->USB_TOP = USB_TOP_CONNECT | USB_TOP_RESET;
        /* clear all pending interrupt */
        udc->USB_INT_STATE = udc->USB_INT_STATE;
        udc->EP_INT_STATE = udc->EP_INT_STATE;
        udc->EP0_INT_STATE = udc->EP0_INT_STATE;
        for (n = 0; n < 4; n ++)
                udc->EPX_INT_STATE[n] = udc->EPX_INT_STATE[n];

        /* enable reset, resume, ep interrupt */
        udc->USB_INT_EN = USB_INT_RESET | USB_INT_RESUME | USB_INT_SUSPEND | USB_INT_EP;
        /* enable all epx interrupt */
        udc->EP_INT_EN = USB_EPn_INT(0) | USB_EPn_INT(CDC_EP_INT_NUM)
        		| USB_EPn_INT(CDC_EP_IN_NUM) | USB_EPn_INT(CDC_EP_OUT_NUM);
        udc->EP0_INT_EN = EP_INT_SETUP | EP_INT_FINISH | EP_INT_INACK | EP_INT_OUTACK;
        udc->USB_ADDR = 0x0000;

        for (n = 0; n < 5; n ++)
                udc->EP_CTRL[n] = 0x00;

        // added by ttlc
        udc->PACKET_SIZE[0] = MAX_PACKAGE_SIZE;
        udc->PACKET_SIZE[1] = MAX_PACKAGE_SIZE;
        udc->EP_SIZE = 0x0;
        udc->EP_DIR = 0x05;	// EP1 in, EP2 out, EP3 in, EP4 out

	udc->EPX_INT_EN[CDC_EP_IN_NUM-1] = EP_INT_INACK;
        udc->EPX_INT_EN[CDC_EP_OUT_NUM-1] = EP_INT_OUTACK;
        udc->EP_ENABLE = USB_EP_EN(CDC_EP_INT_NUM)
        		| USB_EP_EN(CDC_EP_IN_NUM) | USB_EP_EN(CDC_EP_OUT_NUM);

        udc->USB_TOP = USB_TOP_CONNECT;
}

void t1_udc_disconnect(void)
{
	udc->USB_TOP = 0;//USB_TOP_RESET;
}

void write_ep_data(int ep, char * in, int num)
{
        int i;

        for (i = 0; i < num; i ++) {
                udc->EP_FIFO[ep] = *in ++;
        }
        udc->EP_CTRL[ep] = ((num & 0x7F)| 0x80);
}

int read_ep_data(int ep, char * out, int num)
{
	int i = udc->EP_AVAIL[ep];
	if(i<num)
		num = i;
	for(i = 0; i < num; i++) {
		out[i] = udc->EP_FIFO[ep];
	}
	return i;
}

static void Chap9_SetConfiguration( void)
{
        config_value = reqs.wValue;
        udc->EP_CTRL[0] = 0x80;
        usb_running_state = USB_IDLE;
}
static void Chap9_SetAddress(void)
{
        udc->EP_CTRL[0] = 0x80;
        usb_running_state = USB_IDLE;
}

static void Chap9_GetDescriptor(void)
{
       
        switch(usb_running_state){
        case    USB_CTRL_SETUP :
                {
                        usb_running_state = USB_CTRL_IN_TOKEN ;
                        p_ctrl_buf = cdc_req_descriptor(&reqs);
                }
        case    USB_CTRL_IN_TOKEN :
                {
                        uint16_t count = reqs.wLength ;
                        uint16_t temp = 0 ;

                        if( count >= MAX_PACKAGE_SIZE )
                                count = MAX_PACKAGE_SIZE ;
                        while(temp++ < count){
                                udc->EP_FIFO[0] = *p_ctrl_buf++;
                        }
                        udc->EP_CTRL[0] = count | 0x80;
                        reqs.wLength -= count ;
                        if(reqs.wLength == 0)
                                usb_running_state = USB_IDLE ;
                        break ;
                }
        default :
                {
                        usb_running_state = USB_IDLE ;
                        break ;
                }
        }
}

static void Chap9_GetStatus(void)
{
        switch(reqs.bmRequestType) {
        case  (0x80):
                get_status.Device=(remote_wakeup<<1)|0x1;
                udc->EP_FIFO[0] = get_status.Device;
                udc->EP_CTRL[0] = 0x81;
                break;

        case  (0x81):
                get_status.Interface=0;
                udc->EP_FIFO[0] = get_status.Interface;
                udc->EP_CTRL[0] = 0x81;
                break;

        case  (0x82):
                if ((reqs.wIndex & 0x7f) == 0x00)
                        udc->EP_FIFO[0] = get_status.Endpoint[0];
                else if ((reqs.wIndex & 0x8f) <= 4)
                        udc->EP_FIFO[0] = get_status.Endpoint[reqs.wIndex & 0x8f];
                udc->EP_CTRL[0] = 0x81;
                break;

        default:
                break;
        }
        usb_running_state = USB_IDLE ;
}
static void do_feature(int set)
{
        udc->EP_CTRL[0] = 0x80;
        switch (reqs.bmRequestType) {
        case USB_RECIPIENT_DEVICE:
                if (reqs.wValue == USB_DEV_REMOTE_WAKEUP)
                        remote_wakeup = set;
                break;
        case USB_RECIPIENT_ENDPOINT:
                if (reqs.wValue == USB_ENDPOINT_HALT) {
                        if ((reqs.wIndex & 0x7f) == 0x00)
                                get_status.Endpoint[0] = set;
                        else {
                                if ((reqs.wIndex & 0x8f) <= 4) {
                                        get_status.Endpoint[(reqs.wIndex & 0x8f)] = set;
                                }
                        }
                }
                break;

        default:
                break;
        }
}
static void MLsup_StallEP0(void)
{
        usb_running_state = USB_IDLE ;
}
static void MLsup_StallEP1(void)
{
        usb_running_state = USB_IDLE ;
}
static void Chap9_ClearFeature(void)
{
        do_feature(0);
        usb_running_state = USB_IDLE ;
}
static void Chap9_SetFeature(void)
{
        do_feature(1);
        usb_running_state = USB_IDLE ;
}

static void Chap9_SetDescriptor(void)
{
        usb_running_state = USB_IDLE ;
}
static void Chap9_GetConfiguration(void)
{
        udc->EP_FIFO[0] = config_value;
        udc->EP_CTRL[0] = 0x81;
        usb_running_state = USB_IDLE ;
}

static void Chap9_GetInterface(void)
{
        udc->EP_FIFO[0] = get_intf.AlternateSetting;
        udc->EP_CTRL[0] = 0x81;
        usb_running_state = USB_IDLE ;
}

static void Chap9_SetInterface(void)
{
        udc->EP_CTRL[0] = 0x80;
        get_intf.AlternateSetting = (uint8_t)reqs.wValue;
        usb_running_state = USB_IDLE ;
}

static void (* std_dev_request[USB_REQ_MAX])(void) =
{
        Chap9_GetStatus,
        Chap9_ClearFeature,
        MLsup_StallEP0,
        Chap9_SetFeature,
        MLsup_StallEP1,
        Chap9_SetAddress,
        Chap9_GetDescriptor,
        Chap9_SetDescriptor,
        Chap9_GetConfiguration,
        Chap9_SetConfiguration,
        Chap9_GetInterface,
        Chap9_SetInterface,
};

void ep0_handle(void)
{
    uint32_t  temp_epn = udc->EP0_INT_STATE;
	udc->EP0_INT_STATE = temp_epn;

	if(temp_epn & EP_INT_SETUP) {
	        usb_running_state = USB_CTRL_SETUP ;
	        reqs.bmRequestType = udc->SETUP[0];
	        reqs.bRequest = udc->SETUP[1];
	        reqs.wValue = (udc->SETUP[3] << 8) + udc->SETUP[2];
	        reqs.wIndex = (udc->SETUP[5] << 8) + udc->SETUP[4];
	        reqs.wLength = (udc->SETUP[7] << 8) + udc->SETUP[6];
	}
	if(usb_running_state != USB_IDLE){
		unsigned char temp ;
		temp = reqs.bmRequestType & REG_TYPE_MASK ;
		if(temp == REG_TYPE_STANDARD) {
			if(reqs.bRequest < USB_REQ_MAX )
				std_dev_request[reqs.bRequest]( ) ;
		}
		else if(temp == REQ_TYPE_CLASS ) {
			class_request(reqs.bRequest);
	        usb_running_state = USB_IDLE ;
		}
	}
}

#ifndef EP_IN_INT_SUPPORT
void wait_ep_in_complete(int ep)
{
	while(!(udc->EPX_INT_STATE[ep - 1] & EP_INT_INACK)) {
		//udc_cpu_relax();
		if(usb_running_state == USB_RESET)
			break;
	}
	udc->EPX_INT_STATE[ep - 1] = EP_INT_INACK;
}
#endif

#ifdef EP_IN_INT_SUPPORT
static int last_len = 1;
void epn_in_handle(int ep)       // IN Request Received
{
	char buf[MAX_PACKAGE_SIZE];
	int len = cdc_in_completed(buf, MAX_PACKAGE_SIZE);
	if(len)
	{
		write_ep_data(ep, buf, len);
		last_len = len;
	} else {
		if(last_len == MAX_PACKAGE_SIZE) {
			udc->EP_CTRL[ep] = 0x80;
			last_len = 0;
		}
	}
}
#endif

void epn_out_handle(int ep)      // OUT Packet Received
{
	int avl_count = udc->EP_AVAIL[ep];
	char buf[MAX_PACKAGE_SIZE];

	if(avl_count) {
		read_ep_data(ep, buf, avl_count);
		cdc_out_completed(buf, avl_count);
	}
}

void epn_handle(int ep)
{
	uint32_t temp_epn = udc->EPX_INT_STATE[ep-1];
	udc->EPX_INT_STATE[ep-1] = temp_epn;

#ifdef EP_IN_INT_SUPPORT
	if(temp_epn & EP_INT_INACK) {
		epn_in_handle(ep);
	}
	else
#endif
	if(temp_epn & EP_INT_OUTACK )
		epn_out_handle(ep);
	else if(temp_epn&EP_INT_FINISH) {
		int is_in = udc->EP_DIR & _BIT(ep-1);
		if(is_in) {
			;
		} else {
			;
		}
	}
}

//static
void t1_udc_irq_handler(void)
{
	uint32_t temp;

	temp = udc->USB_INT_STATE;
	udc->USB_INT_STATE = temp;

	if(temp & USB_INT_EP){
		int i;
		uint32_t temp_ep = udc->EP_INT_STATE;
		udc->EP_INT_STATE = temp_ep;

		for(i=0; i<=4; i++) {
			if(temp_ep &(1<<i)) {
				if(i==0)
					ep0_handle();
				else
					epn_handle(i);
			}
		}
	}
	if(temp & (USB_INT_RESET | USB_INT_RESUME | USB_INT_SUSPEND)) {
		t1_udc_reset();
	}
}
//static void __delay(uint32 count)
//{
//	while(count --);
//}

int t1_udc_init(void)
{
	int i = 5000;
	udc->USB_TOP = ~USB_TOP_CONNECT;
	while(i--);
	udc->USB_TOP = USB_TOP_CONNECT;
	t1_udc_reset();
	request_irq(IRQ_USB, t1_udc_irq_handler);
	return 0;
}


void disable_udc_interrupt(void)
{
	irq_disable(IRQ_USB);

}

void enable_udc_intterrupt(void)
{
	irq_enable(IRQ_USB);
}



